-- $Id: SphereDistortion.lua 3171 2008-11-06 09:06:29Z det $
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

local SphereDistortion = {}
SphereDistortion.__index = SphereDistortion

local warpShader
local screenLoc, radiusLoc, strengthLoc, centerLoc

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function SphereDistortion.GetInfo()
  return {
    name      = "SphereDistortion",
    backup    = "", --// backup class, if this class doesn't work (old cards,ati's,etc.)
    desc      = "",

    layer     = 1, --// extreme simply z-ordering :x

    --// gfx requirement
    fbo       = true,
    shader    = true,
    distortion= true,
    atiseries = 2,
    intel     = 0,
  }
end

SphereDistortion.Default = {
  layer = 1,
  worldspace = true,

  life     = 25,
  pos      = {0,0,0},
  growth   = 2.5,
  strength = 0.15,

  repeatEffect = false,
  dieGameFrame = math.huge
}

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

local glUniform   = gl.Uniform
local glUseShader = gl.UseShader
local spWorldToScreenCoords = Spring.WorldToScreenCoords

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function SphereDistortion:BeginDrawDistortion()
  glUseShader(warpShader)
  glUniform(screenLoc, 1/vsx,1/vsy )
end

function SphereDistortion:EndDrawDistortion()
  glUseShader(0)
end


function SphereDistortion:DrawDistortion()
  local pos   = self.pos
  local x,y,z = pos[1],pos[2],pos[3]
  local cx,cy = spWorldToScreenCoords(x,y,z)
  glUniform(radiusLoc,   self.radius )
  glUniform(strengthLoc, self.strength )
  glUniform(centerLoc,   cx,cy )

  gl.BeginEnd(GL.QUADS,function()
     gl.TexCoord(-1,1)
    gl.Vertex(x,y,z)
     gl.TexCoord(1,1)
    gl.Vertex(x,y,z)
     gl.TexCoord(1,-1)
    gl.Vertex(x,y,z)
     gl.TexCoord(-1,-1)
    gl.Vertex(x,y,z)
  end)
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function SphereDistortion.Initialize()
  warpShader = gl.CreateShader({
    vertex = [[
      uniform vec2  center;
      uniform float radius;
      uniform vec2  screenInverse;

      varying vec2 texCoord;

      void main()
      {
         gl_Position     = gl_ModelViewMatrix * gl_Vertex;
         gl_Position.xy += gl_MultiTexCoord0.xy * radius;
         gl_Position     = gl_ProjectionMatrix * gl_Position;
         texCoord        = gl_MultiTexCoord0.st;
      }
    ]],
    fragment = [[
      uniform vec2  center;
      uniform float strength;
      uniform vec2  screenInverse;

      varying vec2 texCoord;

      float p1 = gl_ProjectionMatrix[2][2];
      float p2 = gl_ProjectionMatrix[2][3];

      float ConvertZtoEye(float z)
      {
          return p2/(z*2.0-1.0+p1);
      }

      float ConvertEyeToZ(float d)
      {
          return 0.5-0.5*p1+(1.0/(2.0*d))*p2;
      }

      void main(void)
      {
          float dist = length(texCoord);
          if (dist>1.0) {
            discard;
          }else{
            float eyeDepth = ConvertZtoEye(gl_FragCoord.z);
            eyeDepth -= cos(asin(dist))*30.0;
            gl_FragDepth = ConvertEyeToZ(eyeDepth);

            vec2 d = gl_FragCoord.xy - center;
            float distortion = smoothstep(1.0,0.0,dist)*strength;
            vec2 noiseVec    = (d/dist)*screenInverse*distortion;
            gl_FragColor.xyw = vec3(noiseVec,gl_FragCoord.z);

            //float distortion = pow(dist, 1.0/4.0)-dist;
            //float distortion = exp( -0.5*( pow(-dist*6.0+2.5,2.0) ) )*0.25;
            //float distortion = tanh(dist*3.0)-dist;
            //float distortion = smoothstep(0.0,1.0,dist)-dist;
          }
      }
    ]],
    uniform = {
      screenInverse = {1/1280,1/1024},
      strength = 0.15,
    }
  })

  if (warpShader == nil) then
    print(PRIO_MAJOR,"LUPS->SphereDistortion: critical shader error: "..gl.GetShaderLog())
    return false
  end

  screenLoc   = gl.GetUniformLocation(warpShader, 'screenInverse')
  strengthLoc = gl.GetUniformLocation(warpShader, 'strength')
  radiusLoc   = gl.GetUniformLocation(warpShader, 'radius')
  centerLoc   = gl.GetUniformLocation(warpShader, 'center')
end

function SphereDistortion.Finalize()
  if (gl.DeleteShader) then
    gl.DeleteShader(warpShader)
  end
end

function SphereDistortion.ViewResize()
  gl.UseShader(warpShader)
    gl.Uniform(screenLoc,1/vsx,1/vsy)
  gl.UseShader(0)
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function SphereDistortion:Update()
  self.radius = self.radius + self.growth
end

-- used if repeatEffect=true;
function SphereDistortion:ReInitialize()
  self.radius = 0

  self.dieGameFrame = self.dieGameFrame + self.life
end

function SphereDistortion:CreateParticle()
  self.radius = 0

  self.startGameFrame = thisGameFrame
  self.dieGameFrame   = self.startGameFrame + self.life
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

local MergeTable   = MergeTable
local setmetatable = setmetatable

function SphereDistortion.Create(Options)
  local newObject = MergeTable(Options, SphereDistortion.Default)
  setmetatable(newObject,SphereDistortion)  -- make handle lookup
  newObject:CreateParticle()
  return newObject
end

function SphereDistortion:Destroy()
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

return SphereDistortion